package spike.pool.tests;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import spike.pool.implementations.PublicConnectionPool;
import spike.pool.implementations.PublicPoolFactory;
import spike.pool.interfaces.ConnectionSource;

import java.util.concurrent.TimeUnit;

public class PublicPoolFactoryTest extends Assert {

    private PublicConnectionPool pool;
    private PublicConnectionPool keepAlivePool;
    private int min = 10;
    private int max = 100;
    private int keepAliveTime = 500;

    @Before
    public void setUp() throws Exception {
        pool = (PublicConnectionPool) new PublicPoolFactory<>().newConnectionPool(max, new ConnectionSource() {
            @Override
            public Object open() {
                return new String("test connection");
            }

            @Override
            public void close(Object obj) {

            }
        });
        keepAlivePool = (PublicConnectionPool) new PublicPoolFactory<>().newConnectionPool(min, max, keepAliveTime, TimeUnit.MILLISECONDS, new ConnectionSource() {
            @Override
            public Object open() {
                return new String("test connection with keepAliveTime");
            }

            @Override
            public void close(Object obj) {

            }
        });
    }


    private int getPoolSize(PublicConnectionPool pool) {
        try {
            return pool.getPoolSize();
        } catch (Exception e) {
            return -1;
        }
    }

    private int getPermitsNum(PublicConnectionPool pool) {
        try {
            return pool.getAvailablePermits();
        } catch (Exception e) {
            return -1;
        }
    }

    @Test
    public void testGet() throws Exception {
        assertEquals(getPoolSize(pool), max);
        Object[] o = new Object[max];
        for (int i = 0; i < max; i++) {
            assertEquals(getPoolSize(pool), max - i);
            o[i] = pool.get();
        }
        for (int i = 0; i < max; i++) {
            pool.release(o[i]);
        }
        assertEquals(getPoolSize(pool), max);
    }

    @Test
    public void testTryGet() throws Exception {
        assertEquals(getPoolSize(pool), max);
        Object[] o = new Object[max * 2];
        Object ob = pool.tryGet();
        assertNotNull(ob);
        assertEquals(getPoolSize(pool), max - 1);
        pool.release(ob);
        assertEquals(getPoolSize(pool), max);


        for (int i = 0; i < max * 2; i++) {
            o[i] = pool.tryGet();
            assertEquals(getPoolSize(pool), getPermitsNum(pool));
            if (i < max)
                assertNotNull(o[i]);
            else
                assertNull(o[i]);
        }
        for (int i = 0; i < max; i++) {
            pool.release(o[i]);
        }
    }

    @Test
    public void testTryGetWithThreads() throws Exception {
        assertEquals(getPoolSize(pool), max);
        Object ob = pool.tryGet();
        assertNotNull(ob);
        assertEquals(getPoolSize(pool), max - 1);
        pool.release(ob);
        assertEquals(getPoolSize(pool), max);

        for (int i = 0; i < max * 2; i++) {
            new Thread(() -> {
                Object o = pool.tryGet();
                assertNotNull(o);
                if (o != null) {
                    pool.release(o);
                }
            }).start();
        }

    }

    @Test
    public void testTimeUnit() throws Exception {
        assertEquals(getPoolSize(pool), max);
        Object[] o = new Object[max * 2];
        Object ob = pool.tryGet(100, TimeUnit.MILLISECONDS);
        assertNotNull(ob);
        assertEquals(getPoolSize(pool), max - 1);
        pool.release(ob);
        assertEquals(getPoolSize(pool), max);


        for (int i = 0; i < max * 2; i++) {
            o[i] = pool.tryGet(100, TimeUnit.MILLISECONDS);
            assertEquals(getPoolSize(pool), getPermitsNum(pool));
            if (i < max)
                assertNotNull(o[i]);
            else
                assertNull(o[i]);
        }
        for (int i = 0; i < max; i++) {
            pool.release(o[i]);
        }
    }

    @Test
    public void testTimeUnitWithThreads() throws Exception {
        assertEquals(getPoolSize(pool), max);
        Object ob = pool.tryGet(100, TimeUnit.MILLISECONDS);
        assertNotNull(ob);
        assertEquals(getPoolSize(pool), max - 1);
        pool.release(ob);
        assertEquals(getPoolSize(pool), max);

        for (int i = 0; i < max * 2; i++) {
            new Thread(() -> {
                try {
                    Object o = pool.tryGet(1000, TimeUnit.MILLISECONDS);
                    assertNotNull(o);
                    if (o != null) {
                        Thread.sleep(100);
                        pool.release(o);
                    }

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }


            }).start();
        }

    }

    @Test
    public void testKeepAliveTime() throws Exception {
        Object[] o = new Object[max];
        for (int i = 0; i < max; i++) {
            o[i] = keepAlivePool.get();
            assertNotNull(o[i]);
        }
        assertEquals(getPoolSize(keepAlivePool), 0);
        for (int i = 0; i < max; i++) {
            keepAlivePool.release(o[i]);
        }

        assertEquals(getPoolSize(keepAlivePool), max);

    }
}